package org.ndx.majick.properties.impl;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

import org.ndx.majick.properties.Property;
import org.ndx.majick.properties.util.PropertyUtils;

/**
 * Class providing base implementation of the property interface
 * @author Nicolas Delsaux
 */
public class DefaultProperty<Type extends Object> implements Property<Type> {
    /**
     * Contained property value. This field is made protected as a way for subclasses 
     * (in particular vetoable one) to improve setter.
     */
    protected Type value;
    
    /**
     * Used property change listener
     */
    protected transient final PropertyChangeSupport support;
    
    /**
     * Defined property name
     */
    private transient final String name;
    
    /**
     * Supposed property type
     */
    private transient final Class<Type> type;
    
    /**
     * Build a property with all needed infos
     * @param type type of value (as should be returned by {@link #getType()}
     * @param name name of property,as returned by {@link #getName()}
     * @param support change support, used to register listeners and propagate modification events
     */
    @SuppressWarnings("unchecked")
    public DefaultProperty(Class<? extends Type> type, String name, PropertyChangeSupport support) {
        this.type = (Class<Type>) PropertyUtils.unprimitive(type);
        this.name = name;
        this.support = support;
    }

	public DefaultProperty(Class<? extends Type> type, String name, PropertyChangeSupport support, Type value) {
        this(type, name, support);
        set(value);
    }
    
    public void set(Type newValue) {
        if(!equalsValue(newValue)) {
            if(!getType().isInstance(newValue))
                throw new ClassCastException("tried to set as new value for property "+this+" "+newValue);
            Type oldValue = value;
            value = newValue;
            support.firePropertyChange(getName(), oldValue, newValue);
        }
    }

    public Type get() {
        return value;
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        support.addPropertyChangeListener(getName(), listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        support.removePropertyChangeListener(getName(), listener);
    }

    public String getName() {
        return name;
    }
    
    public Class<Type> getType() {
        return type;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj!=null && obj instanceof Property<?>) {
            Property<?> property = (Property<?>) obj;
            return equalsValue(property.get());
        } else {
            return super.equals(obj);
        }
    }

    @Override
    public int hashCode() {
    	if(get()!=null)
    		return get().hashCode();
        return super.hashCode();
    }

    /**
     * Return true if property value is equals to newValue. More precisely, 
     * this method returns true if both {@link #value} and otherValue are null, or if 
     * {@link #value} is not null and otherValue is equals to {@link #value}
     * @param otherValue value we compare with
     * @return
     */
    protected boolean equalsValue(Object otherValue) {
        return get()==null && otherValue==null || get()!=null && get().equals(otherValue);
    }
    
    @Override
    public String toString() {
    	StringBuilder sOut = new StringBuilder();
    	sOut.append("property ");
    	sOut.append(getType().getName());
    	sOut.append(" ");
    	sOut.append(getName());
    	sOut.append(" = ");
    	if(get()==null)
    		sOut.append("null");
    	else
    		sOut.append(get().toString());
    	return sOut.toString();
    }
}
